/*
 *	ME2 C++ SDK
 *	Copyright (c) 2010 _silencer
 *
 *	Permission is hereby granted, free of charge, to any person
 *	obtaining a copy of this software and associated documentation
 *	files (the "Software"), to deal in the Software without
 *	restriction, including without limitation the rights to use,
 *	copy, modify, merge, publish, distribute, sublicense, and/or sell
 *	copies of the Software, and to permit persons to whom the
 *	Software is furnished to do so, subject to the following
 *	conditions:
 *
 *	The above copyright notice and this permission notice shall be
 *	included in all copies or substantial portions of the Software.
 *
 *	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *	EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 *	OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *	NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 *	HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *	WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 *	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 *	OTHER DEALINGS IN THE SOFTWARE.
 */

#include "stdafx.h"

#ifdef _UNICODE
	#define console std::wcout
#else
	#define console std::cout
#endif

typedef HMODULE (WINAPI *LoadLibraryA_t)( LPCSTR lpLibFileName );

struct remote_params
{
	void* load;
	_TCHAR* name;
};

/*
	Write the remote code in assembler, debugging build would else add code to do some runtime checks... crashing the game.
 */
void __declspec(naked) remote_thread( remote_params* param )
{
	__asm
	{
		mov eax, dword ptr [param]
		test eax, eax
		jz end_thread
		mov ecx, dword ptr [eax+4]
		cmp ecx, 0
		je end_thread
		mov eax, dword ptr [eax]
		cmp eax, 0
		je end_thread
		push ecx
		call eax
	end_thread:
		xor eax, eax
		ret
	}
}

/*
	Simple dynamic library injector...
 */
int _tmain( int argc, _TCHAR* argv[] )
{
	console << _T("Launcher starting...") << std::endl;

	STARTUPINFO startup_info;
	PROCESS_INFORMATION process_info;

	memset( &startup_info, 0, sizeof(startup_info) );
	startup_info.cb = sizeof(startup_info);

	/* 
		TODO add some code the set affinity on startup and fix the slow loading bug of the game...
		Startup with a single core, enable other cores after some time.
	 */

	if ( CreateProcess( _T("MassEffect2.exe"), 0, 0, 0, TRUE, CREATE_SUSPENDED, 0, 0, &startup_info, &process_info ) )
	{
		console << _T("Process started...") << std::endl;

		void* start_ptr;
		void* param_ptr;
		void* name_ptr;
		DWORD thread_id;

		/* Drop your dll on the exe or create a shortcut to load your mod... */
		_TCHAR* library_name;

		if ( argc > 1 )
			library_name = argv[1];
		else
			library_name = _T("demo_mod.dll");

		console << _T("Loading ") << library_name << _T("...") << std::endl;

		#ifdef _UNICODE
			int library_name_size( ( wcslen( library_name ) + 1 ) * 2 );
		#else
			int library_name_size( strlen( library_name ) + 1 );
		#endif

		remote_params params;
		
		start_ptr = VirtualAllocEx( process_info.hProcess, 0, 25, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
		param_ptr = VirtualAllocEx( process_info.hProcess, 0, sizeof(remote_params), MEM_COMMIT, PAGE_READWRITE );
		name_ptr = VirtualAllocEx( process_info.hProcess, 0, library_name_size, MEM_COMMIT, PAGE_READWRITE );

		#ifdef _UNICODE
			params.load = GetProcAddress( LoadLibrary( _T("kernel32.dll") ), "LoadLibraryW" );
		#else
			params.load = GetProcAddress( LoadLibrary( _T("kernel32.dll") ), "LoadLibraryA" );
		#endif
		params.name = (_TCHAR*)name_ptr;
		
		/* Debug build uses a jump table for functions! */
		#ifdef _DEBUG
			unsigned int rel_address( *(unsigned int*)((char*)&remote_thread + 1) );
			unsigned int abs_address( rel_address + 5 + (unsigned int)&remote_thread );
			WriteProcessMemory( process_info.hProcess, start_ptr, (void*)abs_address, 28, 0 );
		#else
			WriteProcessMemory( process_info.hProcess, start_ptr, &remote_thread, 28, 0 );
		#endif

		WriteProcessMemory( process_info.hProcess, param_ptr, &params, sizeof(remote_params), 0 );
		WriteProcessMemory( process_info.hProcess, name_ptr, library_name, library_name_size, 0 );

		HANDLE thread = CreateRemoteThread( process_info.hProcess, 0, 0, (LPTHREAD_START_ROUTINE)start_ptr, param_ptr, CREATE_SUSPENDED, &thread_id );

		ResumeThread( process_info.hThread );

		if ( thread )
		{
			/* For now delay loading of mod, so we can attach our debugger :) */
			Sleep( 30000 );

			console << _T("Remote thread started...") << std::endl;

			ResumeThread( thread );

			WaitForSingleObject( thread, INFINITE );

			console << _T("Remote thread ended...") << std::endl;
		}

		WaitForSingleObject( process_info.hThread, INFINITE );

		console << _T("Process ended...") << std::endl;

	}
	else
	{
		console << _T("Process failed to start..") << std::endl;
	}

	console << _T("Launcher done...") << std::endl;

	return 0;
}
